<?php

defined('__MAPEXEC__') or die('No direct access allowed');

class Maps_Geocode_Google extends Maps_Geocode_Common {
  
  private $delay = 0;
  
  /**
   * Extracts the latitude and longitude from the lookup's XML response.
   * @param   mixed         Geographic data
   * @return  Maps_LatLng   Latitude and longitude object
   */
  protected function extractCoords($geoData) {
    $lat = (double) $geoData[2];
    $lng = (double) $geoData[3];
    $status = $geoData[0];
    
    if ($status === '200') {
      return new Maps_LatLng($lat, $lng);
    } else {
      return $status;
    }
  }
  
  /**
   * Gets the geocoding data from the network. Google recommends sending a geocoding
   * request in a loop in case Google returns a status code of 620: Too many
   * requests. Google allows each IP address a certain number of geocoding requests
   * per day, and geocoding too quickly can result in a 620 status. This function
   * delays the geocoding request progressively more each time a 620 status is
   * encountered in case too many requests were made in a row.
   * @param   string  URI to send geocoding request to.
   * @return  mixed   Array when successfull, false when not.
   */
  private function getGeoData($uri) {
    $geoData = false;
    $file = false;
    $pending = true;
    
    while($pending) {
      $file = fopen($uri,'r');
      if ($file) {
        // File was opened successfully
        $geoData = fgetcsv($file);
        d($geoData, '$geoData');
        if ( empty($geoData) ) {
          // Invalid CSV file, exit
          $pending = false;
        } elseif ($geoData[0] === '200') {
          // Geo code was successfull
          $pending = false;
        } elseif ($geoData[0] === '620') {
          // Sent geocode requests too fast
          if ( !$this->incrementDelay() ) {
            // Delay max reached, prevent infinite loop
            $pending = false;
          }
        } else {
          // Failed to geocode
          $geoData = null;
          $pending = false;
          break;
        }
        if ($this->delay > 0) {
          // Delay function execution
          usleep($this->delay);
        }
      } else {
        // File was not opened, exit
        $pending = false;
      }
    }
    
    return $geoData;
  }
  
  /**
   * Increments the geocoding request delay. When the delay reaches
   * 30 seconds, the function returns false on the next call. This
   * helps prevent an infinite loop when geocoding and a 620 status
   * is continually returned.
   * @param   void
   * @return  boolean   True if incremented, false if max delay exceeded
   */
  private function incrementDelay() {
    if ($this->delay < 30000000) {
      $this->delay += 100000;
      return true;
    } else {
      return false;
    }
  }
  
  /**
   * Looks up the geo coordinates of an address. Takes the same arguments as the constructor
   * for Maps_Geo_Location.
   * @return  array   Associative array of lat and lng coordinates.
   */
  protected function lookup(Maps_Geocode_Location $location) {
    $coords = null;
    $geoData = null;
    
    $uri = 'http://maps.google.com/maps/geo?q=' . rawurlencode($location->getFullyQualifiedAddress()) .
        '&key=' . $this->getAPIKey() .
        '&sensor=false&output=csv&oe=' . $this->getEncoding() .
        '&gl=' . $location->getCountry();
    
    $geoData = $this->getGeoData($uri);
    
    if ($geoData) {
      $coords = $this->extractCoords($geoData);
    }
    
    return $coords;
  }
  
}

?>